package com.comunity_cloud.rest;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;

import com.comunity_cloud.exceptions.TransportException;
import com.comunity_cloud.exceptions.UserNameOrEmailConflict;
import com.google.gson.Gson;
import com.user_hosted_storage.dto.ClientDTO;
import com.user_hosted_storage.dto.CommunityDTO;
import com.user_hosted_storage.dto.CommunityMemberDTO;

public class RestClient {

	private static final String HOST = "localhost";

	private static final String BASE_URL_PRIVATE = "http://" + HOST + "/data/client/private/";
	
	private static final String BASE_URL_PUBLIC = "http://" + HOST + "/data/client/public/";

	private static final int RETRIES = 5;

	static {
		trustAllHosts();
	}

	private static Gson gson = new Gson();

	protected static String cachedUserName;

	protected static String cachedPassword;

	protected static int clientVersion;

	public static byte[] SHA256(String text) {
		byte[] md5hash;
		try {
			MessageDigest md;
			md = MessageDigest.getInstance("SHA-256");
			md5hash = new byte[32];
			byte[] bytes = text.getBytes("UTF-8");
			md.update(bytes, 0, bytes.length);
			md5hash = md.digest();
		} catch (NoSuchAlgorithmException e) {
			return null;
		} catch (UnsupportedEncodingException e) {
			return null;
		}
		return md5hash;
	}

	public static ClientDTO login() throws IOException, AuthenticationException, TransportException {
		
		String loginMethod = "loginClient";
		
		ClientDTO client = gson.fromJson(post(BASE_URL_PRIVATE + loginMethod,null,null,false),ClientDTO.class);
		
		return client;
		
	}
	
	public static ClientDTO getClient(long clientId) throws IOException, TransportException {
		try {
			ClientDTO client = gson.fromJson(post(BASE_URL_PRIVATE + "getClient",null,null,false,pair("clientId", clientId)),ClientDTO.class);
			
			return client;
		} catch (AuthenticationException e) {
			return null;
		}		
	}

	public static void setMyIP() throws IOException, TransportException{
		try {
			post("http://" + HOST + "/servlet/client/private/CustomersServlet",null,null,false,pair("action", "updateIP"));
		} catch (AuthenticationException e) {
			
		}
	}
	
	public static void setAcceptPort(int port) throws IOException, TransportException {
		try {
			post("http://" + HOST + "/servlet/client/private/CustomersServlet",null,null,false,pair("action", "updateAcceptPort"), pair("port", port));
		} catch (AuthenticationException e) {
		}
	}
	
	public static void setConnectPort(int port) throws IOException, TransportException {
		try {
			post("http://" + HOST + "/servlet/client/private/CustomersServlet",null,null,false,pair("action", "updateConnectPort"), pair("port", port));
		} catch (AuthenticationException e) {
		}
	}
	
	public static CommunityDTO getCommunity(long communityId) throws IOException, AuthenticationException, TransportException {
		
		return gson.fromJson(post(BASE_URL_PRIVATE + "getCommunity",null,null,false,pair("communityId", communityId)),CommunityDTO.class);
	}
	
	public static ClientDTO registerClient(String userName, String passwordHash, String email, long sharedSize, long maximumBitrate) throws TransportException, IOException, UserNameOrEmailConflict {
		ClientDTO client;
		try {
			client = gson.fromJson(post(BASE_URL_PUBLIC + "registerClient",null,null,false,pair("userName", userName),pair("passwordHash", digest2HexString(SHA256(passwordHash))), pair("email",email), pair("sharedSize", sharedSize),pair("maximumBitrate", maximumBitrate)),ClientDTO.class);
			return client;
		} catch (AuthenticationException e) {
			return null;
		}
	}
	
	public static CommunityMemberDTO[] placeFile(double fileSize, long communityId) throws TransportException, IOException {

		try {
			return gson.fromJson(post(BASE_URL_PRIVATE + "placeFile",null,null,false,pair("fileSize", fileSize),pair("communityId", communityId)),CommunityMemberDTO[].class);
		} catch (AuthenticationException e) {
			return null;
		}
	}
	
	public static CommunityDTO createCommunity(String name) throws TransportException, IOException{

		try {
			CommunityDTO community = gson.fromJson(post(BASE_URL_PRIVATE + "createCommunity",null,null,false,pair("name", name)),CommunityDTO.class);
			return community;
		} catch (AuthenticationException e) {
			return null;
		}
	}
	
	public static void joinCommunity(long communityId, long shareSizeGB) throws TransportException, IOException{

		try {
			post(BASE_URL_PRIVATE + "joinCommunity",null,null,false,pair("communityId", communityId),pair("shareSizeGB",shareSizeGB));
		} catch (AuthenticationException e) {
			return;
		}
	}
	
	public static void inviteToCommunity(long communityId, long clientId) throws TransportException, IOException{

		try {
			post(BASE_URL_PRIVATE + "inviteToCommunity",null,null,false,pair("communityId", communityId),pair("clientId",clientId));
		} catch (AuthenticationException e) {
			return;
		}
	}

	public static CommunityDTO[] getMyCommunities() throws IOException, TransportException{
		try {
			String communitiesJson = post(BASE_URL_PRIVATE + "getMyCommunities",null,null,false);
			CommunityDTO[] communities = gson.fromJson(communitiesJson,CommunityDTO[].class);
			return communities;
		} catch (AuthenticationException e) {
			return null;
		}
	}
	
	public static void setClientVersion(int clientVersion) {
		RestClient.clientVersion = clientVersion;
	}

	public static void setCachedAuth(String userName, String password) {

		cachedPassword = password;
		cachedUserName = userName;
		
		if (userName == null) {
			HttpClientFactory.getThreadSafeClient().getConnectionManager().shutdown();
			HttpClientFactory.shutdown();
		}
	}

	private static String post(String url, String body, String bodyType,
			boolean noRetries, NameValuePair... nameValuePairs)
			throws IOException, TransportException, AuthenticationException {
		ArrayList<NameValuePair> list = new ArrayList<NameValuePair>();
		list.addAll(Arrays.asList(nameValuePairs));
		return post(body, list, url, bodyType, noRetries);
	}

	public static String digest2HexString(byte[] digest) {
		String digestString = "";
		int low, hi;

		for (int i = 0; i < digest.length; i++) {
			low = (digest[i] & 0x0f);
			hi = ((digest[i] & 0xf0) >> 4);
			digestString += Integer.toHexString(hi);
			digestString += Integer.toHexString(low);
		}
		return digestString;
	}

	private static String post(String body, List<NameValuePair> nameValuePairs,
			String initialUrl, String bodyType, boolean noRetries)
			throws IOException, TransportException, AuthenticationException {

		DefaultHttpClient httpclient = HttpClientFactory.getThreadSafeClient();
		HttpParams httpParams = httpclient.getParams();
		HttpConnectionParams.setSoTimeout(httpParams, 20000);
		HttpConnectionParams.setConnectionTimeout(httpParams, 20000);
		httpParams.setParameter("http.protocol.content-charset", "UTF-8");
		httpParams.setParameter("http.socket.timeout", new Integer(20000));
		String and = "?";

		HttpPost httppost;

		String url = initialUrl;
		for (NameValuePair nameValuePair : nameValuePairs) {
			url += and + nameValuePair.getName() + "="
					+ URLEncoder.encode(nameValuePair.getValue(), "UTF-8");
			and = "&";
		}
		httppost = new HttpPost(url);

		if (cachedUserName != null) {
			httppost.setHeader("Authorization", "Basic "+new String(Base64.encodeBase64((cachedUserName+":"+digest2HexString(SHA256(cachedPassword))).getBytes("utf-8"),false)));
		}
		String type = "application/json";
		if (bodyType != null) {
			type = bodyType;
		}
		if (body != null) {

			httppost.setHeader(HTTP.CONTENT_TYPE, type + ";charset="
					+ HTTP.UTF_8);
			httppost.setHeader(HTTP.CONTENT_ENCODING, HTTP.UTF_8);

			StringEntity stringEntity = new StringEntity(body, HTTP.UTF_8);
			stringEntity.setContentEncoding(HTTP.UTF_8);
			stringEntity.setContentType(type);

			httppost.setEntity(stringEntity);

		}

		StringBuilder builder = new StringBuilder("Sending request to server: "
				+ url + " ");
		for (NameValuePair pair : nameValuePairs) {
			if (pair.getName().toLowerCase().contains("password")) {
				continue;
			}
			builder.append("{" + pair.getName() + "=" + pair.getValue() + "}");
		}

		// execute HTTP Post Request
		HttpResponse response = null;
		int countRetries = 0;
		while (countRetries < RETRIES) {
			try {
				response = httpclient.execute(httppost);
				break;
			} catch (Exception e) {
				e.printStackTrace();
				countRetries++;
				if (noRetries) {
					break;
				}
			}
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
			}
		}

		if (response == null) {
			throw new IOException("Exceeded number of retries on post operation");
		}
		HttpEntity entity = response.getEntity();
		String responseStr = null;
		if (entity != null) {
			responseStr = EntityUtils.toString(entity).replace("\r\n", "");

		}
		// check status code
		int statusCode = response.getStatusLine().getStatusCode();
		String statusReason = response.getStatusLine().getReasonPhrase();
		if (statusCode >= 400) {

			switch (statusCode) {
			case HttpStatus.SC_UNAUTHORIZED:
				try {
					if (cachedUserName != null) {
						throw new AuthenticationException();
					}
					login();
					return post(body, nameValuePairs, initialUrl, bodyType,noRetries);
				} catch (Exception e) {
					throw new AuthenticationException(statusReason);
				}

			case HttpStatus.SC_FORBIDDEN:
				try {
					if (cachedUserName == null) {
						throw new AuthenticationException();
					}
					login();
					return post(body, nameValuePairs, initialUrl, bodyType,noRetries);
				} catch (Exception e) {
					throw new AuthenticationException(statusReason);
				}
			default:
				throw new TransportException(statusReason, statusCode, url, responseStr);
			}
		}

		return responseStr;

	}

//	private static String postFile(InputStream body, String fileName, List<NameValuePair> nameValuePairs, String initialUrl, String bodyType, boolean noRetries) throws IOException, TransportException, AuthenticationException {
//
//		DefaultHttpClient httpclient = HttpClientFactory.getThreadSafeClient();
//		HttpParams httpParams = httpclient.getParams();
//		HttpConnectionParams.setSoTimeout(httpParams, 20000);
//		HttpConnectionParams.setConnectionTimeout(httpParams, 20000);
//		httpParams.setParameter("http.protocol.content-charset", "UTF-8");
//		httpParams.setParameter("http.socket.timeout", new Integer(20000));
//		String and = "?";
//
//		HttpPost httppost;
//
//		String url = initialUrl;
//		for (NameValuePair nameValuePair : nameValuePairs) {
//			url += and + nameValuePair.getName() + "="
//					+ URLEncoder.encode(nameValuePair.getValue(), "UTF-8");
//			and = "&";
//		}
//		httppost = new HttpPost(url);
//		httppost.setHeader("Authorization", "Digeest "
//				+ (cachedUserName + ":" + cachedPassword));
//		String type = "application/json";
//		if (bodyType != null) {
//			type = bodyType;
//		}
//		if (body != null) {
//
//			httppost.setHeader(HTTP.CONTENT_TYPE, type + ";charset="
//					+ HTTP.UTF_8);
//			httppost.setHeader(HTTP.CONTENT_ENCODING, HTTP.UTF_8);
//
//			MultipartEntity multipartEntity = new MultipartEntity();
//
//			ContentBody contentBody = new InputStreamBody(body, fileName);
//
//			multipartEntity.addPart("file", contentBody);
//
//			httppost.setEntity(multipartEntity);
//
//		}
//
//		StringBuilder builder = new StringBuilder("Sending request to server: "
//				+ url + " ");
//		for (NameValuePair pair : nameValuePairs) {
//			if (pair.getName().toLowerCase().contains("password")) {
//				continue;
//			}
//			builder.append("{" + pair.getName() + "=" + pair.getValue() + "}");
//		}
//
//		// execute HTTP Post Request
//		HttpResponse response = null;
//		int countRetries = 0;
//		while (countRetries < RETRIES) {
//			try {
//				response = httpclient.execute(httppost);
//				break;
//			} catch (Exception e) {
//				e.printStackTrace();
//				countRetries++;
//				if (noRetries) {
//					break;
//				}
//			}
//			try {
//				Thread.sleep(500);
//			} catch (InterruptedException e) {
//			}
//		}
//
//		if (response == null) {
//			throw new IOException(
//					"Exceeded number of retries on post operation");
//		}
//		HttpEntity entity = response.getEntity();
//		String responseStr = null;
//		if (entity != null) {
//			responseStr = EntityUtils.toString(entity).replace("\r\n", "");
//
//		}
//		// check status code
//		int statusCode = response.getStatusLine().getStatusCode();
//		String statusReason = response.getStatusLine().getReasonPhrase();
//		if (statusCode >= 400) {
//
//			switch (statusCode) {
//
//			default:
//				throw new TransportException(statusReason, statusCode, url,
//						responseStr);
//			}
//		}
//
//		return responseStr;
//
//	}

//	private static String get(String url, NameValuePair... nameValuePairs)
//			throws IOException, TransportException, AuthenticationException {
//		ArrayList<NameValuePair> list = new ArrayList<NameValuePair>();
//		list.addAll(Arrays.asList(nameValuePairs));
//		return get(list, url);
//	}

//	private static String get(List<NameValuePair> nameValuePairs,
//			String initialUrl) throws IOException, TransportException,
//			AuthenticationException {
//
//		DefaultHttpClient httpclient = HttpClientFactory.getThreadSafeClient();
//		HttpParams httpParams = httpclient.getParams();
//		HttpConnectionParams.setSoTimeout(httpParams, 20000);
//		HttpConnectionParams.setConnectionTimeout(httpParams, 20000);
//		httpParams.setParameter("http.protocol.content-charset", "UTF-8");
//		httpclient.getParams().setParameter("http.protocol.content-charset",
//				"UTF-8");
//
//		String url = initialUrl;
//
//		url += "?timestamp=" + System.currentTimeMillis();
//
//		for (NameValuePair nameValuePair : nameValuePairs) {
//			url += "&" + nameValuePair.getName() + "="
//					+ URLEncoder.encode(nameValuePair.getValue(), "UTF-8");
//		}
//
//		HttpGet httpget = new HttpGet(url);
//
//		httpget.setHeader("Authorization", "Digest "
//				+ (cachedUserName + ":" + cachedPassword));
//
//		StringBuilder builder = new StringBuilder("Sending request to server: "
//				+ url + " ");
//		for (NameValuePair pair : nameValuePairs) {
//			builder.append("{" + pair.getName() + "=" + pair.getValue() + "}");
//		}
//
//		// execute HTTP Post Request
//		HttpResponse response = null;
//		int countRetries = 0;
//		while (countRetries < RETRIES) {
//			try {
//				response = httpclient.execute(httpget);
//
//				break;
//			} catch (Exception e) {
//				e.printStackTrace();
//				countRetries++;
//			}
//			try {
//				Thread.sleep(500);
//			} catch (InterruptedException e) {
//			}
//		}
//
//		if (response == null) {
//			throw new IOException("Exceeded number of retries on get operation");
//		}
//
//		// check status code
//		int statusCode = response.getStatusLine().getStatusCode();
//		String statusReason = response.getStatusLine().getReasonPhrase();
//		HttpEntity entity = response.getEntity();
//
//		String responseStr = null;
//		if (entity != null) {
//			responseStr = EntityUtils.toString(entity, "UTF-8");
//		}
//
//		if (statusCode >= 400) {
//
//			switch (statusCode) {
//			case HttpStatus.SC_UNAUTHORIZED:
//				try {
//					if (cachedUserName == null
//							|| initialUrl
//									.startsWith(BASE_URL_PUBLIC + "login")) {
//						throw new AuthenticationException();
//					}
//					login();
//					return get(nameValuePairs, initialUrl);
//				} catch (Exception e) {
//					throw new AuthenticationException(statusReason);
//				}
//
//			case HttpStatus.SC_FORBIDDEN:
//				try {
//					if (cachedUserName == null
//							|| initialUrl
//									.startsWith(BASE_URL_PUBLIC + "login")) {
//						throw new AuthenticationException();
//					}
//					login();
//					return get(nameValuePairs, initialUrl);
//				} catch (Exception e) {
//					throw new AuthenticationException(statusReason);
//				}
//			default:
//				throw ExceptionFactory.createTransportException(statusCode, responseStr, url, statusReason);
//			}
//		}
//
//		return responseStr.replace("\r\n", "");
//
//	}

	// always verify the host - dont check for certificate
	final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
		public boolean verify(String hostname, SSLSession session) {
			return true;
		}
	};

	/**
	 * Trust every server - dont check for any certificate
	 */
	private static void trustAllHosts() {
		// Create a trust manager that does not validate certificate chains
		TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
			public java.security.cert.X509Certificate[] getAcceptedIssuers() {
				return new java.security.cert.X509Certificate[] {};
			}

			public void checkClientTrusted(X509Certificate[] chain,
					String authType) throws CertificateException {
			}

			public void checkServerTrusted(X509Certificate[] chain,
					String authType) throws CertificateException {
			}
		} };

		// Install the all-trusting trust manager
		try {
			SSLContext sc = SSLContext.getInstance("TLS");
			sc.init(null, trustAllCerts, new java.security.SecureRandom());
			HttpsURLConnection
					.setDefaultSSLSocketFactory(sc.getSocketFactory());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static BasicNameValuePair pair(String key, Object value) {
		return new BasicNameValuePair(key, value.toString());
	}

	public static void main(String[] args) {
		try {
			
//			registerClient("alex2", "zxc", "asd2@asd.com", 123, 100);
//			registerClient("alex3", "zxc", "asd3@asd.com", 123, 100);
//			registerClient("alex4", "zxc", "asd4@asd.com", 123, 100);
			
//			setCachedAuth("alex3", "zxc");
//			ClientDTO client = login();
//			createCommunity("TestCommunity");
//			inviteToCommunity(33, 26);
			
//			joinCommunity(33, 100);
			
			System.out.println(getMyCommunities().length);
			CommunityDTO comm = getCommunity(33);
			System.out.println(comm.getMembers().size());
//			CommunityDTO[] communities = getMyCommunities();
//			for (CommunityDTO communityDTO : communities) {
//				System.out.println(communityDTO.getName());
//			}
		} catch (IOException | TransportException | AuthenticationException e ) {
			e.printStackTrace();
		}
	}
}
